最近公司项目有一个对Project导入导出的操作,现在市面上能同时对Project进行导入导出的除了微软自带的Microsoft.Office.Interop.MSProject,还有就是Aspose.Tasks for .NET。但因为后者是收费软件且破解版的现阶段只到18.11,只支持.net Framework,而我们的项目是用的netcore,在对Aspose.Tasks破解版进行测试的过程中始终提示“The opreation is not allowed in evaluation model”,未花费过多精力去研究Aspose.Tasks。转战Interop.MSProject,网上还是有相关参考资料,以下是我们的代码,(未进行方法封装优化):
需要用到COM组件:Microsoft.Office.Core以及 Microsoft.Office.Interop.MSProject
导出代码
public static void Export(List<ImportPlanMppModel> result, string fileSavePath)
{
Microsoft.Office.Interop.MSProject.ApplicationClass prj = null;
try
{
//创建COM(MSProject)
prj = new Microsoft.Office.Interop.MSProject.ApplicationClass();
prj.Visible = true;
//创建Project
prj.FileNew(Type.Missing, Type.Missing, Type.Missing, false);
//prj.SetField("责任人", "", true);
//prj.SetField("责任人", "", true);
//prj.CustomFieldValueListAdd(Microsoft.Office.Interop.MSProject.PjCustomField.pjCustomProjectEnterpriseText1, "123", "11", "111", "1232", "456");
//prj.ShowAddNewColumn(10);
//prj.AddNewColumn(7);
Microsoft.Office.Interop.MSProject.Project myProject = prj.ActiveProject;
Microsoft.Office.Interop.MSProject.PjFileFormat format = Microsoft.Office.Interop.MSProject.PjFileFormat.pjMPP;//format定义
object missing = System.Reflection.Missing.Value;//missing值
//myProject.AutoTrack = true;
//.........................................
//myProject.HoursPerDay = 8;
//myProject.HoursPerWeek = 56;
//myProject.DaysPerMonth = 30;
//Microsoft.Office.Interop.MSProject.WeekDays weedday = myProject.Calendar.WeekDays;
//weedday[1].set_Working(true);
//weedday[7].set_Working(true);
//myProject.ShowCriticalSlack = 0;
List<Microsoft.Office.Interop.MSProject.Task> taskList = new List<Microsoft.Office.Interop.MSProject.Task>();
foreach (var item in result)
{
Microsoft.Office.Interop.MSProject.Task task = null;
//System.Threading.Thread.Sleep(1000);
task = myProject.Tasks.Add(item.TaskName, Type.Missing);//item.Id
task.Duration = item.TaskDuration;
if (item.TaskPlanStartDate.HasValue)
{
task.Start = item.TaskPlanStartDate;
}
if (item.TaskPlanEndDate.HasValue)
{
task.Finish = item.TaskPlanEndDate;
}
if (item.TaskActualStartDate.HasValue)
{
task.ActualStart = item.TaskActualStartDate;
}
if (item.TaskActualEndDate.HasValue)
{
task.ActualFinish = item.TaskActualEndDate;
}
if (item.TaskOutlineLevel > 0)
{
task.OutlineLevel = Convert.ToInt16(item.TaskOutlineLevel);
}
//task.Milestone = dr["IsMTask"].ToString() == "1" ? true : false;//是否里程碑:0=否、1=是
task.WBS = item.WBS;
//负责人
task.Text1 = item.ResponsibleUserName;
//平米单价
if (item.SquareMetrePrice.HasValue)
{
task.Number1 = Convert.ToDouble(item.SquareMetrePrice);
}
//标记列
task.Notes = item.Id.ToString();
//资源名称
//task.ResourceNames = "资源名称1,资源名称2";
标记列
//task.SetField(Microsoft.Office.Interop.MSProject.PjField.pjTaskNotes, "Task1");
资源名称
//task.SetField(Microsoft.Office.Interop.MSProject.PjField.pjTaskResourceNames, "wenzhixing,wen,zhi");
//task.SetField(Microsoft.Office.Interop.MSProject.PjField.pjTaskText1, "负责人");
//task.SetField(Microsoft.Office.Interop.MSProject.PjField.pjTaskNumber1, "10.231");
//task.Rollup = true;
taskList.Add(task);
}
for (int i = 0; i < taskList.Count; i++)
{
Microsoft.Office.Interop.MSProject.Task task = taskList[i];
var item = result.FirstOrDefault(x=>x.Id.ToString() == task.Notes);
///前置任务
if (!string.IsNullOrWhiteSpace(item.TaskPredecessorsIdStr))
{
for (int j = 0; j < item.TaskPredecessorsIdStr.Split(',').Length; j++)
{
var dependencTaskId = item.TaskPredecessorsIdStr.Split(',')[j];
var dependencTask = taskList.FirstOrDefault(x => x.Notes == dependencTaskId);
if (dependencTask != null)
{
var dependencTaskType = Enum.Parse<Microsoft.Office.Interop.MSProject.PjTaskLinkType>(item.TaskPredecessorsTypeStr.Split(',')[j]);
task.TaskDependencies.Add(dependencTask, dependencTaskType, item.TaskPredecessorsDurationStr.Split(',')[j]);
}
}
}
}
//保存到指定目录
prj.FileSaveAs(fileSavePath, format, missing, missing, missing, missing, missing, missing, missing, "MSProject.mpp", missing, missing, missing, missing, missing, missing, missing, missing, missing);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (prj != null)
{
try
{
//退出COM组件
prj.FileClose(Microsoft.Office.Interop.MSProject.PjSaveType.pjDoNotSave);
prj.Quit(Microsoft.Office.Interop.MSProject.PjSaveType.pjDoNotSave);
}
catch
{
}
}
}
}
导入代码
public static List<ImportPlanMppModel> ImportTasks(string filename)
{
List<ImportPlanMppModel> result = null;
if (System.IO.File.Exists(filename) == false)
{
throw new FriendlyException("未找到文件信息");
}
result = new List<ImportPlanMppModel>();
ProjectReader reader = ProjectReaderUtility.getProjectReader(filename);
ProjectFile file = reader.read(filename);
foreach (net.sf.mpxj.Task task in file.Tasks.ToIEnumerable())
{
if (task.ID.toString() == "0") //自动创建的节点,一般为文件名,不需要
{
continue;
}
ImportPlanMppModel model = new ImportPlanMppModel();
model.Id = task.ID.intValue();
model.Guid = Guid.Parse(task.GUID.toString());
model.UniqueId = task.UniqueID.intValue();
model.TaskName = task.Name;
model.TaskDuration = task.Duration.Duration;
if (task.ParentTask != null && Convert.ToInt32(task.OutlineLevel.toString()) > 1) //是否根节点,mpp文件必须只有一个根节点,否则会报错
{
model.ParentId = task.ParentTask.ID.intValue();
model.ParentGuid = Guid.Parse(task.ParentTask.GUID.toString());
}
model.TaskPlanStartDate = task.Start.ToNullableDateTime(); //DateTime.Parse(string.Format("{0:d}", task.Start.ToDateTime()));
model.TaskPlanEndDate = task.Finish.ToNullableDateTime();
model.TaskActualStartDate = task.ActualStart.ToNullableDateTime();
model.TaskActualEndDate = task.ActualFinish.ToNullableDateTime();
model.TaskOutlineLevel = task.OutlineLevel.intValue();
bool isHasChildTask = false;
if (task.HasChildTasks())
{
isHasChildTask = true;
}
model.IsHasChildTask = isHasChildTask;
model.ResponsibleUserName = task.GetText(1);
model.SquareMetrePrice = task.GetNumber(1).ToNullableDecimal();
model.WBS = task.WBS;
string beforeTaskId = string.Empty;
string beforeTaskGuid = string.Empty;
string beforeTaskType = string.Empty;
string beforeTaskDuration = string.Empty;
if (task.Predecessors != null && task.Predecessors.isEmpty() == false)
{
foreach (Relation relation in task.Predecessors.ToIEnumerable())
{
beforeTaskId += relation.TargetTask.ID.intValue() + ",";
beforeTaskGuid += relation.TargetTask.GUID.toString() + ",";
beforeTaskType += relation.Type.Value + ",";
beforeTaskDuration += relation.Lag.Duration + ",";
}
}
model.TaskPredecessorsIdStr = beforeTaskId.TrimEnd(',');
model.TaskPredecessorsGuIdStr = beforeTaskGuid.TrimEnd(',');
model.TaskPredecessorsTypeStr = beforeTaskType.TrimEnd(',');
model.TaskPredecessorsDurationStr = beforeTaskDuration.TrimEnd(',');
result.Add(model);
}
return result;
}
发布运行问题
我们的程序版本是使用的32位的project 2016 professional(office也是2016专业增强版的32位),在VS编译调试没有问题,发布到IIS进行测试时,在代码进行到
//创建COM(MSProject)
prj = new Microsoft.Office.Interop.MSProject.ApplicationClass();
会触发异常:"System.Runtime.InteropServices.COMException (0x80010001): Retrieving the COM class factory for component with CLSID {36D27C48-A1E8-11D3-BA55-00C04F72F325} failed due to the following error: 80010001 被呼叫方拒绝接收呼叫。 (0x80010001 (RPC_E_CALL_REJECTED))"。查询了很多相关资料,绝大部分都是说可能是账户权限问题,给除的解决办法也都是去对Com组件进行赋权。
网上大部分解决方案如下:
-在命令行中输入:dcomcnfg,会显示出“组件服务”管理器
-打开“组件服务->计算机->我的电脑->DCOM 配置”,找到“Microsoft Project”,单击右键,选择“属性”
-在“属性”对话框中单击“标识”选项卡,选择“交互式用户””
-然后找到“安全”,把下面所有的权限都选择自定义,然后添加各种用户全部权限。
但对我们项目的代码没有作用,机缘巧合我自己本机是office2010专业增强版64位,project 也是64位的2010专业版,我这边部署到IIS后导出在相同代码提示的是:"Retrieving the COM class factory for component with CLSID {36D27C48-A1E8-11D3-BA55-00C04F72F325} failed due to the following error: 80070005 拒绝访问"。按照如上的网上大部分解决方案配置后依旧有问题,后续对DCOM组件 project basic进行标识配置时,我选择了指定具体的用户
且在IIS进程池的进程模型的标识选择了:LocalSystem,再进行测试时发现可以正常导出,且导出时不会弹出project应用窗口。
在搜索解决方案时,看到过 "HymanLiuTS"的 错误处理(一)—— 被呼叫方拒绝接收呼叫。 (异常来自 HRESULT:0x80010001 (RPC_E_CALL_REJECTED)),在文章里给出了微软的一个解决方案,方案2 引入IOleMessageFilter,从根本上杜绝这种异常情况的出现,
”如何:修复“应用程序正忙”和“被调用者拒绝了调用”错误“,参考官方代码修改了项目里的相应位置的代码,具体修改如下:
MessageFilter.Register();//新增异常处理代码
//创建COM(MSProject)
prj = new Microsoft.Office.Interop.MSProject.ApplicationClass();
if (prj != null)
{
try
{
//退出COM组件
prj.FileClose(Microsoft.Office.Interop.MSProject.PjSaveType.pjDoNotSave);
prj.Quit(Microsoft.Office.Interop.MSProject.PjSaveType.pjDoNotSave);
}
catch
{
}
}
MessageFilter.Revoke();//新增异常处理代码
再将DCOM的标识从指定具体账号更改为交互式用户,iis配置不变也能导出成功,不过在导出文件时会弹出project应用窗口。
注:上述两种处理方式在32位的project 2016上始终未能解决,若您有解决方案,请你留言向您讨教。